package com.wxxymaker.edu.controller.api;

import com.blade.ioc.annotation.Inject;
import com.blade.kit.EncryptKit;
import com.blade.kit.StringKit;
import com.blade.kit.UUID;
import com.blade.mvc.annotation.*;
import com.blade.mvc.http.ByteBody;
import com.blade.mvc.http.Response;
import com.blade.mvc.multipart.FileItem;
import com.blade.mvc.ui.RestResponse;
import com.wxxymaker.edu.model.entity.ImageInfo;
import com.wxxymaker.edu.model.entity.Material;
import com.wxxymaker.edu.roles.StudentRoles;
import com.wxxymaker.edu.roles.TeacherRoles;
import com.wxxymaker.edu.service.ImageService;
import com.wxxymaker.edu.service.MaterialService;
import com.wxxymaker.edu.utils.Const;
import com.wxxymaker.edu.utils.FileUtils;
import com.wxxymaker.edu.utils.TokenUtils;
import com.wxxymaker.edu.utils.enums.FileType;
import com.wxxymaker.edu.utils.enums.ImageType;
import com.wxxymaker.edu.utils.enums.MaterialType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

/**
 * @AUTHOR soft
 * @DATE 2018/9/8 11:23
 * @DESCRIBE 文件处理接口
 */
@Slf4j
@Path("/file")
public class FileHandlerApi {

    @Inject
    private ImageService imageService;
    @Inject
    private MaterialService materialService;

    // %d kid  %s filename
    public static final String[] TYPEMAP = {"",
            "/exam/%s/paper/%s/paper.doc", // 试卷路径
            "/exam/%s/paper/%s/paper_result.doc", // 试卷答案路径
            "/exam/%s/paper/%s/user_result/%s",  // 用户答案路径
            "/video/course/%s/task/%s"}; // 视频文件路径
    /**
     * 下载文件
     * @param k 课程id
     * @param d 文件夹名
     * @param f 文件名
     * @param t 文件类型 1试卷 2试卷答案 3学生试卷解答
     */
    @StudentRoles
    @Route("/download")
    public void download(Response response,
                         @Param Integer k,
                         @Param String d,
                         @Param String f,
                         @Param Integer t,
                         @Param String name) {
        try {
            File file = new File(Const.CLASSPATH, String.format(TYPEMAP[t], k, d, f));
            if (file.exists()) {
                if (StringKit.isEmpty(f)) f = "a.doc";
                if (StringKit.isNotEmpty(name)) f = getFixedFilename(f, name);
                response.header("Content-Disposition", "attachment; filename="+
                        new String(f.getBytes(StandardCharsets.UTF_8), "ISO8859_1"))
                        .header("Content-Length", String.valueOf(file.length()))
                        .contentType(StringKit.mimeType(f));
                response.body(ByteBody.of(file));
            } else {
                response.json(RestResponse.fail("文件不存在！"));
            }
        } catch (Exception e) {
            log.error("下载文件出错！{}", e.getMessage());
            response.json(RestResponse.fail(e.getMessage()));
        }
    }

    @StudentRoles
    @Route("/video/:kid")
    public void video(Response response,
                      @PathParam Integer kid,
                      @Param String v,
                      @HeaderParam("Range") String range){
        String path = "/video/course/" + kid  + "/task/" + v;
        try {
            File file = new File(Const.CLASSPATH, path);
            this.rangeMax(response, file, range);
        } catch (Exception e) {
            log.error("视频文件读取错误！{}", e.getMessage());
            response.json(RestResponse.fail(e.getMessage()));
        }
    }

    @JSON
    @StudentRoles
    @PostRoute("/upload")
    public RestResponse normalUpload(@MultipartParam FileItem file,
                                     @Param(defaultValue = "0") int kid,
                                     @Param(defaultValue = "0") int pid,
                                     @Param String path,
                                     @Param(defaultValue = "image") String type) {
        try {
            UploadResult result = upload0(file, type, kid, 0, pid,null, path);
            return RestResponse.ok(result);
        } catch (IOException e) {
            log.error("上传文件出错！{}", e.getMessage());
            return RestResponse.fail(e.getMessage());
        }
    }

    /**
     * 文件上传
     * @param file 文件
     * @param type 上传文件类型 @see com.wxxymaker.edu.utils.enums.FileType
     * @param kid  课程id
     * @param tid  任务id
     * @param pid  试卷id
     * @param name 试卷答案前缀名
     * @param path 相对upload目录下的路径 有值则固定在该路径下
     */
    @JSON
    @TeacherRoles
    @PostRoute("/t/upload")
    public RestResponse upload(@MultipartParam FileItem file,
                               @Param(defaultValue = "image") String type,
                               @Param(defaultValue = "0") int kid,
                               @Param(defaultValue = "0") int tid,
                               @Param(defaultValue = "0") int pid,
                               @Param String name,
                               @Param String path) {
        try {
            UploadResult result = upload0(file, type, kid, tid, pid, name, path);
            return RestResponse.ok(result);
        } catch (IOException e) {
            log.error("上传文件出错！{}", e.getMessage());
            return RestResponse.fail(e.getMessage());
        }
    }

    /**
     * 3.8改 不在此进行视频上传处理
     * @param file 文件
     * @param type 上传文件类型 @see com.wxxymaker.edu.utils.enums.FileType
     * @param kid  课程id
     * @param tid  任务id
     * @param pid  试卷id
     * @param name 自定义文件前缀名
     * @param path 相对upload目录下的路径 有值则固定在该路径下
     */
    private UploadResult upload0(FileItem file, String type,
                                 int kid, int tid, int pid,
                                 String name, String path) throws IOException {
        Integer uid         = TokenUtils.currentUser().getId();
        String  filename    = file.getFileName();
        String  newFilename = getRandomFilename(filename);
        String  filePath    = "/upload";

        UploadResult result = new UploadResult();

        if (FileType.COURSE_ICON.equals(type)) {  // 课程图标保存到固定位置
            filePath = "/upload/course/" + kid + "/icon/" + newFilename;
            FileUtils.removeDirOtherFile(filePath);
        } else if (FileType.BIG_IMAGE.equals(type)) { // 主页大图
            filePath = "/upload/image/big_image/" + newFilename;
            ImageInfo imageInfo = new ImageInfo();
            imageInfo.setPath(filePath);
            imageInfo.setType(ImageType.BIG_IMAGE.getType());

            result.setId(imageService.add(imageInfo).getId());
        } else if (FileType.USER_ICON.equals(type)) { // 用户头像
            filePath = "/upload/user/" + uid + "/icon/" + newFilename;
            FileUtils.removeDirOtherFile(filePath);
        } else if (FileType.TEST_PAPER.equals(type)) {  // 试卷文件
//            String randomFilename = "";
//            if (StringKit.isNotEmpty(name)) randomFilename = name+".doc";
//            else randomFilename = getRandomFilename(filename, ".doc");
            filePath = "/temp/exam/" + kid + "/paper/" + path + "/paper.doc";
        } else if (FileType.TEST_RESULT.equals(type)) { // 试卷答案
            filePath = "/exam/" + kid + "/paper/" + path + "/paper_result.doc";
        } else if (FileType.TEST_USER_RESULT.equals(type)) { // 用户试卷答案
            String randomFilename = EncryptKit.md5("user_result", String.valueOf(uid))+".doc";
            filePath = "/exam/" + kid + "/paper/"+path+"/user_result/" + randomFilename;
        } else {
            Material material = new Material();
            material.setCourseId(kid);
            material.setTaskId(tid);
            material.setUserId(uid);
            material.setDowns(0);
            material.setSize((file.getLength() / 1024.0 / 1024.0));
            material.setFilename(filename);

            if (FileType.MATERIAL.equals(type)) { // 课外资源文件
                filePath = "/temp/upload/course/" + kid + "/other/" + newFilename;

                material.setPath(filePath);
                material.setType(MaterialType.OTHER);
                result.setPayload(material);
            } else if (FileType.TASK.equals(type)) { // 课程任务资源
                filePath = "/video/course/" + kid  + "/task/" + newFilename;
                String videoPath = "/file/video/" + kid + "?v=" + newFilename;
                material.setPath(videoPath);
                result.setPath(videoPath);
                material.setType(MaterialType.TASK);
                Material add = materialService.add(material);
                result.setId(add.getId());
            }
        }
        File newFile = new File(Const.CLASSPATH, filePath);
        FileUtils.createFolder(newFile);
        file.moveTo(newFile);
//        FileUtils.move(file.getFile(), filePath);
        if (StringKit.isEmpty(result.getPath())) result.setPath(filePath);
        if (StringKit.isEmpty(result.getFilename())) result.setFilename(filename);
        return result;
    }

    private String getRandomFilename(String filename) {
        String name = UUID.captchaChar(14);
        String type = filename.substring(filename.lastIndexOf("."));
        return name + type;
    }

    private String getFixedFilename(String filename, String name) {
        String type = filename.substring(filename.lastIndexOf("."));
        return name + type;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private class UploadResult {
        private Integer id;       // 资源id
        private String  path;     // web路径
        private String  filename; // 文件名
        private Object  payload;  // 携带的数据
    }

    private static final Integer BUFFER_LENGTH = 1024*32; // 32kb
    /**
     * 断点续传支持
     * @param response 响应体
     * @param file 视频文件
     * @param range 开始部分
     */
    private void rangeMax(Response response,
                          File file,
                          String range) throws IOException {
        String filename = file.getName();
        if (file.exists()) {
            long size = Files.size(file.toPath());
            long start = 0;
            long end = size - 1;

            if (StringKit.isNotEmpty(range)) {
                start = Long.parseLong(range.substring(range.indexOf("=") + 1, range.lastIndexOf("-")));
                String ends = range.substring(range.lastIndexOf("-") + 1);
                end = ends.isEmpty() ? end : Long.parseLong(ends);

                start = start < 0 ? 0 : start;
                end = end > size - 1 ? size - 1 : end;
            }

            long contentLength = end - start + 1;
            response.header("Content-Disposition", String.format("inline;filename=%s", filename))
                    .header("Accept-Ranges", "bytes")
                    .header("Last-Modified", String.valueOf(Files.getLastModifiedTime(file.toPath())))
                    .header("Content-Range", String.format("bytes %s-%s/%s", start, end, size))
                    .header("Content-Length", String.valueOf(contentLength))
                    .contentType(StringKit.mimeType(filename))
                    .status(206);

            long len;
            long residue = contentLength;
            byte[] bytes = new byte[BUFFER_LENGTH];
            RandomAccessFile read = new RandomAccessFile(file, "r");
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            read.seek(start);
            while ((len = read.read(bytes)) != -1 && residue > 0) {
                out.write(bytes, 0, (int) (residue < len ? residue : len));
                residue -= len;
            }
            read.close();
            response.body(ByteBody.of(out.toByteArray()));
            out.close();
        } else {
            response.json(RestResponse.fail(400, "文件不存在！"));
        }
    }
}
